home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Games Extra 1996 September
/
Amiga Games Extra CD-ROM 9-1996.iso
/
userbox
/
publicdomain
/
vim-4.2
/
src
/
gui_x11.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-06-16
|
58KB
|
2,379 lines
/* vi:set ts=4 sw=4:
*
* VIM - Vi IMproved by Bram Moolenaar
* GUI/Motif support by Robert Webb
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
*/
#include <X11/keysym.h>
#include <X11/Xatom.h>
#include <X11/StringDefs.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "option.h"
#include "ops.h"
#define VIM_NAME "vim"
#define VIM_CLASS "Vim"
/* Default resource values */
#define DFLT_FONT "7x13"
#define DFLT_MENU_BG_COLOR "gray77"
#define DFLT_MENU_FG_COLOR "black"
#define DFLT_SCROLL_BG_COLOR "gray60"
#define DFLT_SCROLL_FG_COLOR "gray77"
Widget vimShell = (Widget)NULL;
static XtAppContext app_context;
static Atom Atom_WM_DELETE_WINDOW;
static Pixel gui_x11_get_color __ARGS((char_u *));
static void gui_x11_set_color __ARGS((GC, Pixel));
static void gui_x11_set_font __ARGS((GC, Font));
static void gui_x11_check_copy_area __ARGS((void));
static void gui_x11_update_menus_recurse __ARGS((GuiMenu *, int));
static void gui_x11_request_selection_cb __ARGS((Widget, XtPointer,
Atom *, Atom *, XtPointer,
long_u *, int *));
static Boolean gui_x11_convert_selection_cb __ARGS((Widget, Atom *, Atom *,
Atom *, XtPointer *,
long_u *, int *));
static void gui_x11_lose_ownership_cb __ARGS((Widget, Atom *));
static void gui_x11_wm_protocol_handler __ARGS((Widget, XtPointer,
XEvent *, Boolean *));
static void gui_x11_invert_area __ARGS((int, int, int, int));
static void gui_x11_yank_selection __ARGS((int, int, int, int));
static void gui_x11_get_word_boundaries __ARGS((GuiSelection *, int, int));
static int gui_x11_get_line_end __ARGS((int));
static void gui_x11_update_selection __ARGS((GuiSelection *, int, int, int,
int));
#define char_class(c) (c <= ' ' ? ' ' : iswordchar(c))
#define invert_rectangle(r, c, nr, nc) \
XFillRectangle(gui.dpy, gui.wid, gui.invert_gc, \
FILL_X(c), FILL_Y(r), (nc) * gui.char_width, \
(nr) * gui.char_height)
static struct
{
KeySym key_sym;
char_u vim_code0;
char_u vim_code1;
} special_keys[] =
{
{XK_Up, 'k', 'u'},
{XK_Down, 'k', 'd'},
{XK_Left, 'k', 'l'},
{XK_Right, 'k', 'r'},
{XK_F1, 'k', '1'},
{XK_F2, 'k', '2'},
{XK_F3, 'k', '3'},
{XK_F4, 'k', '4'},
{XK_F5, 'k', '5'},
{XK_F6, 'k', '6'},
{XK_F7, 'k', '7'},
{XK_F8, 'k', '8'},
{XK_F9, 'k', '9'},
{XK_F10, 'k', ';'},
{XK_F11, 'F', '1'},
{XK_F12, 'F', '2'},
{XK_F13, 'F', '3'},
{XK_F14, 'F', '4'},
{XK_F15, 'F', '5'},
{XK_F16, 'F', '6'},
{XK_F17, 'F', '7'},
{XK_F18, 'F', '8'},
{XK_F19, 'F', '9'},
{XK_F20, 'F', 'A'},
{XK_F21, 'F', 'B'},
{XK_F22, 'F', 'C'},
{XK_F23, 'F', 'D'},
{XK_F24, 'F', 'E'},
{XK_F25, 'F', 'F'},
{XK_F26, 'F', 'G'},
{XK_F27, 'F', 'H'},
{XK_F28, 'F', 'I'},
{XK_F29, 'F', 'J'},
{XK_F30, 'F', 'K'},
{XK_F31, 'F', 'L'},
{XK_F32, 'F', 'M'},
{XK_F33, 'F', 'N'},
{XK_F34, 'F', 'O'},
{XK_F35, 'F', 'P'}, /* keysymdef.h defines up to F35 */
{XK_Help, '%', '1'},
{XK_Undo, '&', '8'},
{XK_BackSpace, 'k', 'b'},
{XK_Insert, 'k', 'I'},
{XK_Delete, 'k', 'D'},
{XK_Home, 'k', 'h'},
{XK_End, '@', '7'},
{XK_Prior, 'k', 'P'},
{XK_Next, 'k', 'N'},
{XK_Print, '%', '9'},
/* Keypad keys: */
#ifdef XK_KP_Left
{XK_KP_Left, 'k', 'l'},
{XK_KP_Right, 'k', 'r'},
{XK_KP_Up, 'k', 'u'},
{XK_KP_Down, 'k', 'd'},
{XK_KP_Insert, 'k', 'I'},
{XK_KP_Delete, 'k', 'D'},
{XK_KP_Home, 'k', 'h'},
{XK_KP_End, '@', '7'},
{XK_KP_Prior, 'k', 'P'},
{XK_KP_Next, 'k', 'N'},
#endif
/* End of list marker: */
{(KeySym)0, 0, 0}
};
#define XtNboldColor "boldColor"
#define XtCBoldColor "BoldColor"
#define XtNitalicColor "italicColor"
#define XtCItalicColor "ItalicColor"
#define XtNunderlineColor "underlineColor"
#define XtCUnderlineColor "UnderlineColor"
#define XtNcursorColor "cursorColor"
#define XtCCursorColor "CursorColor"
#define XtNboldFont "boldFont"
#define XtCBoldFont "BoldFont"
#define XtNitalicFont "italicFont"
#define XtCItalicFont "ItalicFont"
#define XtNboldItalicFont "boldItalicFont"
#define XtCBoldItalicFont "BoldItalicFont"
#define XtNscrollbarWidth "scrollbarWidth"
#define XtCScrollbarWidth "ScrollbarWidth"
#define XtNmenuHeight "menuHeight"
#define XtCMenuHeight "MenuHeight"
/* Resources for setting the foreground and background colors of menus */
#define XtNmenuBackground "menuBackground"
#define XtCMenuBackground "MenuBackground"
#define XtNmenuForeground "menuForeground"
#define XtCMenuForeground "MenuForeground"
/* Resources for setting the foreground and background colors of scrollbars */
#define XtNscrollBackground "scrollBackground"
#define XtCScrollBackground "ScrollBackground"
#define XtNscrollForeground "scrollForeground"
#define XtCScrollForeground "ScrollForeground"
/*
* X Resources:
*/
static XtResource vim_resources[] =
{
{
XtNforeground,
XtCForeground,
XtRPixel,
sizeof(Pixel),
XtOffsetOf(Gui, norm_pixel),
XtRString,
XtDefaultForeground
},
{
XtNbackground,
XtCBackground,
XtRPixel,
sizeof(Pixel),
XtOffsetOf(Gui, back_pixel),
XtRString,
XtDefaultBackground
},
{
XtNboldColor,
XtCBoldColor,
XtRPixel,
sizeof(Pixel),
XtOffsetOf(Gui, bold_pixel),
XtRString,
XtDefaultForeground
},
{
XtNitalicColor,
XtCItalicColor,
XtRPixel,
sizeof(Pixel),
XtOffsetOf(Gui, ital_pixel),
XtRString,
XtDefaultForeground
},
{
XtNunderlineColor,
XtCUnderlineColor,
XtRPixel,
sizeof(Pixel),
XtOffsetOf(Gui, underline_pixel),
XtRString,
XtDefaultForeground
},
{
XtNcursorColor,
XtCCursorColor,
XtRPixel,
sizeof(Pixel),
XtOffsetOf(Gui, cursor_pixel),
XtRString,
XtDefaultForeground
},
{
XtNfont,
XtCFont,
XtRString,
sizeof(String *),
XtOffsetOf(Gui, dflt_font),
XtRImmediate,
XtDefaultFont
},
{
XtNboldFont,
XtCBoldFont,
XtRString,
sizeof(String *),
XtOffsetOf(Gui, dflt_bold_fn),
XtRImmediate,
""
},
{
XtNitalicFont,
XtCItalicFont,
XtRString,
sizeof(String *),
XtOffsetOf(Gui, dflt_ital_fn),
XtRImmediate,
""
},
{
XtNboldItalicFont,
XtCBoldItalicFont,
XtRString,
sizeof(String *),
XtOffsetOf(Gui, dflt_boldital_fn),
XtRImmediate,
""
},
{
XtNgeometry,
XtCGeometry,
XtRString,
sizeof(String *),
XtOffsetOf(Gui, geom),
XtRImmediate,
""
},
{
XtNreverseVideo,
XtCReverseVideo,
XtRBool,
sizeof(Bool),
XtOffsetOf(Gui, rev_video),
XtRImmediate,
(XtPointer) False
},
{
XtNborderWidth,
XtCBorderWidth,
XtRInt,
sizeof(int),
XtOffsetOf(Gui, border_width),
XtRImmediate,
(XtPointer) 2
},
{
XtNscrollbarWidth,
XtCScrollbarWidth,
XtRInt,
sizeof(int),
XtOffsetOf(Gui, scrollbar_width),
XtRImmediate,
(XtPointer) SB_DEFAULT_WIDTH
},
{
XtNmenuHeight,
XtCMenuHeight,
XtRInt,
sizeof(int),
XtOffsetOf(Gui, menu_height),
XtRImmediate,
(XtPointer) MENU_DEFAULT_HEIGHT
},
{
XtNmenuForeground,
XtCMenuForeground,
XtRPixel,
sizeof(Pixel),
XtOffsetOf(Gui, menu_fg_pixel),
XtRString,
DFLT_MENU_FG_COLOR
},
{
XtNmenuBackground,
XtCMenuBackground,
XtRPixel,
sizeof(Pixel),
XtOffsetOf(Gui, menu_bg_pixel),
XtRString,
DFLT_MENU_BG_COLOR
},
{
XtNscrollForeground,
XtCScrollForeground,
XtRPixel,
sizeof(Pixel),
XtOffsetOf(Gui, scroll_fg_pixel),
XtRString,
DFLT_SCROLL_FG_COLOR
},
{
XtNscrollBackground,
XtCScrollBackground,
XtRPixel,
sizeof(Pixel),
XtOffsetOf(Gui, scroll_bg_pixel),
XtRString,
DFLT_SCROLL_BG_COLOR
},
};
/*
* This table holds all the X GUI command line options allowed. This includes
* the standard ones so that we can skip them when vim is started without the
* GUI (but the GUI might start up later).
* When changing this, also update doc/vim_gui.txt and the usage message!!!
*/
static XrmOptionDescRec cmdline_options[] =
{
/* We handle these options ourselves */
{"-bg", ".background", XrmoptionSepArg, NULL},
{"-background", ".background", XrmoptionSepArg, NULL},
{"-fg", ".foreground", XrmoptionSepArg, NULL},
{"-foreground", ".foreground", XrmoptionSepArg, NULL},
{"-bold", ".boldColor", XrmoptionSepArg, NULL},
{"-italic", ".italicColor", XrmoptionSepArg, NULL},
{"-ul", ".underlineColor", XrmoptionSepArg, NULL},
{"-underline", ".underlineColor", XrmoptionSepArg, NULL},
{"-cursor", ".cursorColor", XrmoptionSepArg, NULL},
{"-fn", ".font", XrmoptionSepArg, NULL},
{"-font", ".font", XrmoptionSepArg, NULL},
{"-boldfont", ".boldFont", XrmoptionSepArg, NULL},
{"-italicfont", ".italicFont", XrmoptionSepArg, NULL},
{"-geom", ".geometry", XrmoptionSepArg, NULL},
{"-geometry", ".geometry", XrmoptionSepArg, NULL},
{"-reverse", "*reverseVideo", XrmoptionNoArg, "True"},
{"-rv", "*reverseVideo", XrmoptionNoArg, "True"},
{"+reverse", "*reverseVideo", XrmoptionNoArg, "False"},
{"+rv", "*reverseVideo", XrmoptionNoArg, "False"},
{"-display", ".display", XrmoptionSepArg, NULL},
{"-iconic", "*iconic", XrmoptionNoArg, "True"},
{"-name", ".name", XrmoptionSepArg, NULL},
{"-bw", ".borderWidth", XrmoptionSepArg, NULL},
{"-borderwidth", ".borderWidth", XrmoptionSepArg, NULL},
{"-sw", ".scrollbarWidth", XrmoptionSepArg, NULL},
{"-scrollbarwidth", ".scrollbarWidth", XrmoptionSepArg, NULL},
{"-mh", ".menuHeight", XrmoptionSepArg, NULL},
{"-menuheight", ".menuHeight", XrmoptionSepArg, NULL},
{"-xrm", NULL, XrmoptionResArg, NULL}
};
static int gui_argc = 0;
static char **gui_argv = NULL;
/*
* Call-back routines.
*/
void
gui_x11_timer_cb(timed_out, interval_id)
XtPointer timed_out;
XtIntervalId *interval_id;
{
*((int *)timed_out) = TRUE;
}
void
gui_x11_visibility_cb(w, dud, event, bool)
Widget w;
XtPointer dud;
XEvent *event;
Boolean *bool;
{
if (event->type != VisibilityNotify)
return;
gui.visibility = event->xvisibility.state;
/*
* When we do an XCopyArea(), and the window is partially obscured, we want
* to receive an event to tell us whether it worked or not.
*/
XSetGraphicsExposures(gui.dpy, gui.text_gc,
gui.visibility != VisibilityUnobscured);
}
void
gui_x11_expose_cb(w, dud, event, bool)
Widget w;
XtPointer dud;
XEvent *event;
Boolean *bool;
{
XExposeEvent *gevent;
if (event->type != Expose)
return;
gevent = (XExposeEvent *)event;
gui_redraw(gevent->x, gevent->y, gevent->width, gevent->height);
/* Clear the border areas if needed */
if (gevent->x < FILL_X(0))
XClearArea(gui.dpy, gui.wid, 0, 0, FILL_X(0), 0, False);
if (gevent->y < FILL_Y(0))
XClearArea(gui.dpy, gui.wid, 0, 0, 0, FILL_Y(0), False);
if (gevent->x > FILL_X(Columns))
XClearArea(gui.dpy, gui.wid, FILL_X(Columns), 0, 0, 0, False);
if (gevent->y > FILL_Y(Rows))
XClearArea(gui.dpy, gui.wid, 0, FILL_Y(Rows), 0, 0, False);
if (gui.selection.state != SELECT_CLEARED)
gui_x11_redraw_selection(gevent->x, gevent->y, gevent->width,
gevent->height);
}
void
gui_x11_resize_window_cb(w, dud, event, bool)
Widget w;
XtPointer dud;
XEvent *event;
Boolean *bool;
{
if (event->type != ConfigureNotify)
return;
gui_resize_window(event->xconfigure.width, event->xconfigure.height);
/* Make sure the border strips on the right and bottom get cleared. */
XClearArea(gui.dpy, gui.wid, FILL_X(Columns), 0, 0, 0, False);
XClearArea(gui.dpy, gui.wid, 0, FILL_Y(Rows), 0, 0, False);
}
void
gui_x11_focus_change_cb(w, data, event, bool)
Widget w;
XtPointer data;
XEvent *event;
Boolean *bool;
{
if (event->type == FocusIn)
gui.in_focus = TRUE;
else
gui.in_focus = FALSE;
if (gui.row == gui.cursor_row && gui.col == gui.cursor_col)
INVALIDATE_CURSOR();
gui_update_cursor();
}
void
gui_x11_key_hit_cb(w, dud, event, bool)
Widget w;
XtPointer dud;
XEvent *event;
Boolean *bool;
{
XKeyPressedEvent *ev_press;
char_u string[3], string2[3];
KeySym key_sym;
int num, i;
ev_press = (XKeyPressedEvent *)event;
num = XLookupString(ev_press, (char *)string, sizeof(string),
&key_sym, NULL);
if (key_sym == XK_space)
string[0] = ' '; /* Otherwise Ctrl-Space doesn't work */
/* Check for Alt/Meta key (Mod1Mask) */
if (num == 1 && (ev_press->state & Mod1Mask))
{
/*
* Before we set the 8th bit, check to make sure the user doesn't
* already have a mapping defined for this sequence. We determine this
* by checking to see if the input would be the same without the
* Alt/Meta key.
*/
ev_press->state &= ~Mod1Mask;
if (XLookupString(ev_press, (char *)string2, sizeof(string2),
&key_sym, NULL) == 1 && string[0] == string2[0])
string[0] |= 0x80;
ev_press->state |= Mod1Mask;
}
#if 0
if (num == 1 && string[0] == CSI) /* this doesn't work yet */
{
string[1] = CSI;
string[2] = CSI;
num = 3;
}
#endif
/* Check for special keys, making sure BS and DEL are recognised. */
if (num == 0 || key_sym == XK_BackSpace || key_sym == XK_Delete)
{
for (i = 0; special_keys[i].key_sym != (KeySym)0; i++)
{
if (special_keys[i].key_sym == key_sym)
{
string[0] = CSI;
string[1] = special_keys[i].vim_code0;
string[2] = special_keys[i].vim_code1;
num = 3;
}
}
}
/* Unrecognised key */
if (num == 0)
return;
/* Special keys (and a few others) may have modifiers */
if (num == 3 || key_sym == XK_space || key_sym == XK_Tab
|| key_sym == XK_Return || key_sym == XK_Linefeed
|| key_sym == XK_Escape)
{
string2[0] = CSI;
string2[1] = KS_MODIFIER;
string2[2] = 0;
if (ev_press->state & ShiftMask)
string2[2] |= MOD_MASK_SHIFT;
if (ev_press->state & ControlMask)
string2[2] |= MOD_MASK_CTRL;
if (ev_press->state & Mod1Mask)
string2[2] |= MOD_MASK_ALT;
if (string2[2] != 0)
add_to_input_buf(string2, 3);
}
if (num == 1 && string[0] == Ctrl('C'))
{
trash_input_buf();
got_int = TRUE;
}
add_to_input_buf(string, num);
}
void
gui_x11_mouse_cb(w, dud, event, bool)
Widget w;
XtPointer dud;
XEvent *event;
Boolean *bool;
{
static XtIntervalId timer = (XtIntervalId)0;
static int timed_out = TRUE;
int button;
int repeated_click = FALSE;
int x, y;
int_u x_modifiers;
int_u vim_modifiers;
int checkfor;
if (event->type == MotionNotify)
{
x = event->xmotion.x;
y = event->xmotion.y;
button = MOUSE_DRAG;
x_modifiers = event->xmotion.state;
}
else
{
x = event->xbutton.x;
y = event->xbutton.y;
if (event->type == ButtonPress)
{
/* Handle multiple clicks */
if (!timed_out)
{
XtRemoveTimeOut(timer);
repeated_click = TRUE;
}
timed_out = FALSE;
timer = XtAppAddTimeOut(app_context, (long_u)p_mouset,
gui_x11_timer_cb, &timed_out);
switch (event->xbutton.button)
{
case Button1: button = MOUSE_LEFT; break;
case Button2: button = MOUSE_MIDDLE; break;
case Button3: button = MOUSE_RIGHT; break;
default:
return; /* Unknown button */
}
}
else if (event->type == ButtonRelease)
button = MOUSE_RELEASE;
else
return; /* Unknown mouse event type */
x_modifiers = event->xbutton.state;
}
vim_modifiers = 0x0;
if (x_modifiers & ShiftMask)
vim_modifiers |= MOUSE_SHIFT;
if (x_modifiers & ControlMask)
vim_modifiers |= MOUSE_CTRL;
if (x_modifiers & Mod1Mask) /* Alt or Meta key */
vim_modifiers |= MOUSE_ALT;
/* If an x11 selection is in progress, finish it */
if (gui.selection.state == SELECT_IN_PROGRESS)
{
gui_x11_process_selection(button, x, y, repeated_click, vim_modifiers);
return;
}
/* Determine which mouse settings to look for based on the current mode */
switch (State)
{
case NORMAL_BUSY:
case NORMAL: checkfor = MOUSE_NORMAL; break;
case VISUAL: checkfor = MOUSE_VISUAL; break;
case REPLACE:
case INSERT: checkfor = MOUSE_INSERT; break;
case HITRETURN: checkfor = MOUSE_RETURN; break;
/*
* On the command line, use the X11 selection on all lines but the
* command line.
*/
case CMDLINE:
if (Y_2_ROW(y) < cmdline_row)
checkfor = ' ';
else
checkfor = MOUSE_COMMAND;
break;
default:
checkfor = ' ';
break;
};
/*
* Allow selection of text in the command line in "normal" modes.
*/
if ((State == NORMAL || State == NORMAL_BUSY ||
State == INSERT || State == REPLACE) &&
Y_2_ROW(y) >= gui.num_rows - p_ch)
checkfor = ' ';
/*
* If the mouse settings say to not use the mouse, use the X11 selection.
* But if Visual is active, assume that only the Visual area will be
* selected.
*/
if (!mouse_has(checkfor) && !VIsual_active)
{
/* If the selection is done, allow the right button to extend it */
if (gui.selection.state == SELECT_DONE && button == MOUSE_RIGHT)
{
gui_x11_process_selection(button, x, y, repeated_click,
vim_modifiers);
return;
}
/* Allow the left button to start the selection */
else if (button == MOUSE_LEFT)
{
gui_x11_start_selection(button, x, y, repeated_click,
vim_modifiers);
return;
}
}
if (gui.selection.state != SELECT_CLEARED)
gui_mch_clear_selection();
gui_send_mouse_event(button, x, y, repeated_click, vim_modifiers);
}
/*
* End of call-back routines
*/
/*
* Parse the GUI related command-line arguments. Any arguments used are
* deleted from argv, and *argc is decremented accordingly. This is called
* when vim is started, whether or not the GUI has been started.
*/
void
gui_mch_prepare(argc, argv)
int *argc;
char **argv;
{
int arg;
int i;
/*
* Move all the entries in argv which are relevant to X into gui_argv.
*/
gui_argc = 0;
gui_argv = (char **)lalloc(*argc * sizeof(char *), FALSE);
if (gui_argv == NULL)
return;
gui_argv[gui_argc++] = argv[0];
arg = 1;
while (arg < *argc)
{
/* Look for argv[arg] in cmdline_options[] table */
for (i = 0; i < XtNumber(cmdline_options); i++)
if (strcmp(argv[arg], cmdline_options[i].option) == 0)
break;
if (i < XtNumber(cmdline_options))
{
/* Found match in table, so move it into gui_argv */
gui_argv[gui_argc++] = argv[arg];
if (--*argc > arg)
{
vim_memmove(&argv[arg], &argv[arg + 1], (*argc - arg)
* sizeof(char *));
if (cmdline_options[i].argKind != XrmoptionNoArg)
{
/* Move the options argument as well */
gui_argv[gui_argc++] = argv[arg];
if (--*argc > arg)
vim_memmove(&argv[arg], &argv[arg + 1], (*argc - arg)
* sizeof(char *));
}
}
}
else
arg++;
}
}
/*
* Initialise the X GUI. Create all the windows, set up all the call-backs
* etc.
*/
int
gui_mch_init()
{
Widget AppShell;
long_u gc_mask;
XGCValues gc_vals;
Pixel tmp_pixel;
int x, y, mask;
unsigned w, h;
XtToolkitInitialize();
app_context = XtCreateApplicationContext();
gui.dpy = XtOpenDisplay(app_context, 0, VIM_NAME, VIM_CLASS,
cmdline_options, XtNumber(cmdline_options),
#ifndef XtSpecificationRelease
(Cardinal*)&gui_argc, gui_argv);
#else
#if XtSpecificationRelease == 4
(Cardinal*)&gui_argc, gui_argv);
#else
&gui_argc, gui_argv);
#endif
#endif
vim_free(gui_argv);
if (gui.dpy == NULL)
{
x = full_screen;
full_screen = FALSE; /* use fprintf() */
EMSG("cannot open display");
full_screen = x;
return FAIL;
}
/* Uncomment this to enable synchronous mode for debugging */
/* XSynchronize(gui.dpy, True); */
/*
* So converters work.
*/
XtInitializeWidgetClass(applicationShellWidgetClass);
XtInitializeWidgetClass(topLevelShellWidgetClass);
/*
* The applicationShell is created as an unrealized
* parent for multiple topLevelShells. The topLevelShells
* are created as popup children of the applicationShell.
* This is a recommendation of Paul Asente & Ralph Swick in
* _X_Window_System_Toolkit_ p. 677.
*/
AppShell = XtVaAppCreateShell(VIM_NAME, VIM_CLASS,
applicationShellWidgetClass, gui.dpy,
NULL);
/*
* Get the application resources
*/
XtVaGetApplicationResources(AppShell, &gui,
vim_resources, XtNumber(vim_resources), NULL);
/*
* Check validity of resources
*/
if (gui.border_width < 0)
gui.border_width = 0;
/* For reverse video, swap foreground and background colours */
if (gui.rev_video)
{
tmp_pixel = gui.norm_pixel;
gui.norm_pixel = gui.back_pixel;
gui.back_pixel = tmp_pixel;
}
/* Create shell widget to put vim in */
vimShell = XtVaCreatePopupShell("VIM",
topLevelShellWidgetClass, AppShell,
XtNborderWidth, 0,
NULL);
/*
* Check that none of the colours are the same as the background color
*/
if (gui.norm_pixel == gui.back_pixel)
{
gui.norm_pixel = gui_x11_get_color((char_u *)"White");
if (gui.norm_pixel == gui.back_pixel)
gui.norm_pixel = gui_x11_get_color((char_u *)"Black");
}
if (gui.bold_pixel == gui.back_pixel)
gui.bold_pixel = gui.norm_pixel;
if (gui.ital_pixel == gui.back_pixel)
gui.ital_pixel = gui.norm_pixel;
if (gui.underline_pixel == gui.back_pixel)
gui.underline_pixel = gui.norm_pixel;
if (gui.cursor_pixel == gui.back_pixel)
gui.cursor_pixel = gui.norm_pixel;
/*
* Set up the GCs. The font attributes will be set in gui_init_font().
*/
gc_mask = GCForeground | GCBackground;
gc_vals.foreground = gui.norm_pixel;
gc_vals.background = gui.back_pixel;
gui.text_gc = XCreateGC(gui.dpy, DefaultRootWindow(gui.dpy), gc_mask,
&gc_vals);
gc_vals.foreground = gui.back_pixel;
gc_vals.background = gui.norm_pixel;
gui.back_gc = XCreateGC(gui.dpy, DefaultRootWindow(gui.dpy), gc_mask,
&gc_vals);
gc_mask |= GCFunction;
gc_vals.foreground = gui.norm_pixel ^ gui.back_pixel;
gc_vals.background = gui.norm_pixel ^ gui.back_pixel;
gc_vals.function = GXxor;
gui.invert_gc = XCreateGC(gui.dpy, DefaultRootWindow(gui.dpy), gc_mask,
&gc_vals);
gui.visibility = VisibilityUnobscured;
gui.selection.atom = XInternAtom(gui.dpy, "VIM_SELECTION", False);
/*
* Set up the fonts. This call will also set the window to the correct
* size for the used font.
*/
gui.norm_font = NULL;
gui.bold_font = NULL;
gui.ital_font = NULL;
gui.boldital_font = NULL;
if (gui_init_font() == FAIL)
return FAIL;
/* Now adapt the supplied(?) geometry-settings */
/* Added by Kjetil Jacobsen <kjetilja@stud.cs.uit.no> */
if (gui.geom != NULL && *gui.geom != NUL)
{
mask = XParseGeometry((char *)gui.geom, &x, &y, &w, &h);
if (mask & WidthValue)
Columns = w;
if (mask & HeightValue)
Rows = h;
/*
* Set the (x,y) position of the main window only if specified in the
* users geometry, so we get good defaults when they don't. This needs
* to be done before the shell is popped up.
*/
if (mask & (XValue|YValue))
XtVaSetValues(vimShell, XtNgeometry, gui.geom, NULL);
}
gui.num_cols = Columns;
gui.num_rows = Rows;
gui_reset_scroll_region();
gui_mch_create_widgets();
/* Configure the desired scrollbars */
gui_init_which_components(NULL);
/* Actually open the window */
XtPopup(vimShell, XtGrabNone);
gui_mch_set_winsize();
gui.wid = gui_mch_get_wid();
/* Add a callback for the Close item on the window managers menu */
Atom_WM_DELETE_WINDOW = XInternAtom(gui.dpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(gui.dpy, XtWindow(vimShell), &Atom_WM_DELETE_WINDOW, 1);
XtAddEventHandler(vimShell, NoEventMask, True, gui_x11_wm_protocol_handler,
NULL);
#ifdef ENABLE_EDITRES
/*
* Enable editres protocol (see editres(1))
* Usually will need to add -lXmu to the linker line as well.
*/
{
extern void _XEditResCheckMessages();
XtAddEventHandler(vimShell, 0, True, _XEditResCheckMessages,
(XtPointer)NULL);
}
#endif
return OK;
}
void
gui_mch_exit()
{
XtCloseDisplay(gui.dpy);
}
/*
* While unmanaging and re-managing scrollbars etc, we don't want the resize
* callback to be called, so this function is used to disable this callback,
* and then re-enable it afterwards. XSync() makes sure we don't register the
* callback before the server has finished processing any resize events we have
* created, otherwise we can get in a loop where the window flashes between two
* sizes, since resize_window_cb() calls set_winsize().
*/
void
gui_x11_use_resize_callback(widget, enabled)
Widget widget;
int enabled;
{
if (enabled)
{
XSync(gui.dpy, False);
XtAddEventHandler(widget, StructureNotifyMask, FALSE,
gui_x11_resize_window_cb, (XtPointer)0);
XtAddEventHandler(widget, ExposureMask, FALSE, gui_x11_expose_cb,
(XtPointer)0);
}
else
{
XtRemoveEventHandler(widget, StructureNotifyMask, FALSE,
gui_x11_resize_window_cb, (XtPointer)0);
XtRemoveEventHandler(widget, ExposureMask, FALSE, gui_x11_expose_cb,
(XtPointer)0);
XSync(gui.dpy, False);
}
}
/*
* Initialise vim to use the font with the given name. Return FAIL if the font
* could not be loaded, OK otherwise.
*/
int
gui_mch_init_font(font_name)
char_u *font_name;
{
XFontStruct *font = NULL;
if (font_name == NULL)
{
/*
* If none of the fonts in 'font' could be loaded, try the one set in
* the X resource, and finally just try using DFLT_FONT, which will
* hopefully always be there.
*/
font = XLoadQueryFont(gui.dpy, (char *)gui.dflt_font);
if (font == NULL)
font_name = (char_u *)DFLT_FONT;
font_name = (char_u *)DFLT_FONT;
}
if (font == NULL)
font = XLoadQueryFont(gui.dpy, (char *)font_name);
if (font == NULL)
return FAIL;
if (font->max_bounds.width != font->min_bounds.width)
{
sprintf((char *)IObuff, "Font \"%s\" is not fixed-width",
(char *)font_name);
emsg(IObuff);
XFreeFont(gui.dpy, font);
return FAIL;
}
if (gui.norm_font != NULL)
XFreeFont(gui.dpy, gui.norm_font);
gui.norm_font = font;
gui.char_width = font->max_bounds.width;
gui.char_height = font->ascent + font->descent;
gui.char_ascent = font->ascent;
#ifdef DEBUG
printf("Font Information for '%s':\n", font_name);
printf(" w = %d, h = %d, ascent = %d, descent = %d\n", gui.char_width,
gui.char_height, gui.char_ascent, font->descent);
printf(" max ascent = %d, max descent = %d, max h = %d\n",
font->max_bounds.ascent, font->max_bounds.descent,
font->max_bounds.ascent + font->max_bounds.descent);
printf(" min lbearing = %d, min rbearing = %d\n",
font->min_bounds.lbearing, font->min_bounds.rbearing);
printf(" max lbearing = %d, max rbearing = %d\n",
font->max_bounds.lbearing, font->max_bounds.rbearing);
printf(" leftink = %d, rightink = %d\n",
(font->min_bounds.lbearing < 0),
(font->max_bounds.rbearing > gui.char_width));
printf("\n");
#endif
/*
* Try to load other fonts for bold, italic, and bold-italic.
* We should also try to work out what font to use for these when they are
* not specified by X resources, but we don't yet.
*/
if (gui.dflt_bold_fn != NULL && *gui.dflt_bold_fn != NUL)
gui.bold_font = XLoadQueryFont(gui.dpy, (char *)gui.dflt_bold_fn);
if (gui.dflt_ital_fn != NULL && *gui.dflt_ital_fn != NUL)
gui.ital_font = XLoadQueryFont(gui.dpy, (char *)gui.dflt_ital_fn);
if (gui.dflt_boldital_fn != NULL && *gui.dflt_boldital_fn != NUL)
gui.boldital_font = XLoadQueryFont(gui.dpy, (char *)gui.dflt_boldital_fn);
/* Must set the appropriate fonts for all the GCs */
gui_x11_set_font(gui.text_gc, gui.norm_font->fid);
gui_x11_set_font(gui.back_gc, gui.norm_font->fid);
/*
* Set the window sizes if the window is already visible.
*/
if (vimShell != (Widget)NULL && XtIsRealized(vimShell))
{
WIN *wp;
gui_mch_set_winsize();
/* Force the scrollbar heights to get updated when a font is changed */
if (gui.which_scrollbars[SB_LEFT])
{
for (wp = firstwin; wp; wp = wp->w_next)
wp->w_scrollbar.update[SB_LEFT] = SB_UPDATE_HEIGHT;
gui_mch_update_scrollbars(SB_UPDATE_HEIGHT, SB_LEFT);
}
if (gui.which_scrollbars[SB_RIGHT])
{
for (wp = firstwin; wp; wp = wp->w_next)
wp->w_scrollbar.update[SB_RIGHT] = SB_UPDATE_HEIGHT;
gui_mch_update_scrollbars(SB_UPDATE_HEIGHT, SB_RIGHT);
}
}
return OK;
}
/*
* Return OK if the key with the termcap name "name" is supported.
*/
int
gui_mch_haskey(name)
char_u *name;
{
int i;
for (i = 0; special_keys[i].key_sym != (KeySym)0; i++)
if (name[0] == special_keys[i].vim_code0 &&
name[1] == special_keys[i].vim_code1)
return OK;
return FAIL;
}
/*
* Return the text window-id and display. Only required for X-based GUI's
*/
int
gui_get_x11_windis(win, dis)
Window *win;
Display **dis;
{
*win = XtWindow(vimShell);
*dis = gui.dpy;
return OK;
}
void
gui_mch_beep()
{
XBell(gui.dpy, 0);
}
void
gui_mch_flash()
{
/* Do a visual beep by reversing the foreground and background colors */
XFillRectangle(gui.dpy, gui.wid, gui.invert_gc, 0, 0,
FILL_X(Columns) + gui.border_offset,
FILL_Y(Rows) + gui.border_offset);
XSync(gui.dpy, False);
mch_delay(20L, TRUE); /* wait 1/50 of a second */
XFillRectangle(gui.dpy, gui.wid, gui.invert_gc, 0, 0,
FILL_X(Columns) + gui.border_offset,
FILL_Y(Rows) + gui.border_offset);
}
/*
* Iconify the GUI window.
*/
void
gui_mch_iconify()
{
XIconifyWindow(gui.dpy, XtWindow(vimShell), DefaultScreen(gui.dpy));
}
/*
* Return the Pixel value (color) for the given color name. This routine was
* pretty much taken from example code in the Silicon Graphics OSF/Motif
* Programmer's Guide.
*/
static Pixel
gui_x11_get_color(name)
char_u *name;
{
XrmValue from, to;
from.size = STRLEN(name) + 1;
if (from.size < sizeof(String))
from.size = sizeof(String);
from.addr = (char *)name;
to.addr = NULL;
XtConvert(vimShell, XtRString, &from, XtRPixel, &to);
if (to.addr != NULL)
return (Pixel) *((Pixel *)to.addr);
else
return (Pixel)NULL;
}
/*
* Set the current text font (gc should be either gui.text_gc or gui.back_gc)
*/
static void
gui_x11_set_font(gc, fid)
GC gc;
Font fid;
{
static Font prev_text_fid = (Font) -1;
static Font prev_back_fid = (Font) -1;
if (gc == gui.text_gc && fid != prev_text_fid)
{
XSetFont(gui.dpy, gc, fid);
prev_text_fid = fid;
}
else if (gc == gui.back_gc && fid != prev_back_fid)
{
XSetFont(gui.dpy, gc, fid);
prev_back_fid = fid;
}
}
/*
* Set the current text color (gc should be either gui.text_gc or gui.back_gc)
*/
static void
gui_x11_set_color(gc, pixel)
GC gc;
Pixel pixel;
{
static Pixel prev_text_pixel = (Pixel) -1;
static Pixel prev_back_pixel = (Pixel) -1;
if (gc == gui.text_gc && pixel != prev_text_pixel)
{
XSetForeground(gui.dpy, gc, pixel);
prev_text_pixel = pixel;
}
else if (gc == gui.back_gc && pixel != prev_back_pixel)
{
XSetBackground(gui.dpy, gc, pixel);
prev_back_pixel = pixel;
}
}
/*
* Draw the cursor.
*/
void
gui_mch_draw_cursor()
{
/* Only write to the screen after LinePointers[] has been initialized */
if (screen_cleared && NextScreen != NULL)
{
/* Clear the selection if we are about to write over it */
if (gui.selection.state == SELECT_DONE
&& gui.row >= gui.selection.start.lnum
&& gui.row <= gui.selection.end.lnum)
gui_mch_clear_selection();
if (gui.row < screen_Rows && gui.col < screen_Columns)
gui_mch_outstr_nowrap(LinePointers[gui.row] + gui.col,
1, FALSE, TRUE);
if (!gui.in_focus)
{
gui_x11_set_color(gui.text_gc, gui.cursor_pixel);
XDrawRectangle(gui.dpy, gui.wid, gui.text_gc, FILL_X(gui.col),
FILL_Y(gui.row), gui.char_width - 1, gui.char_height - 1);
}
}
}
/*
* Catch up with any queued X events. This may put keyboard input into the
* input buffer, call resize call-backs, trigger timers etc. If there is
* nothing in the X event queue (& no timers pending), then we return
* immediately.
*/
void
gui_mch_update()
{
while(XtAppPending(app_context) && !is_input_buf_full())
XtAppProcessEvent(app_context, XtIMAll);
}
/*
* The main GUI input routine. Waits for a character from the keyboard.
* wtime == -1 Wait forever.
* wtime == 0 Don't wait.
* wtime > 0 Wait wtime milliseconds for a character.
* Returns OK if a character was found to be available within the given time,
* or FAIL otherwise.
*/
int
gui_mch_wait_for_chars(wtime)
int wtime;
{
XtIntervalId timer = (XtIntervalId)0;
XtIntervalId updatescript_timer = (XtIntervalId)0;
/*
* Make these static, in case gui_x11_timer_cb is called after leaving
* this function (otherwise a random value on the stack may be changed).
*/
static int timed_out;
static int do_updatescript;
timed_out = FALSE;
do_updatescript = FALSE;
/*
* If we're going to wait a bit, update the menus for the current State.
*/
if (wtime != 0)
gui_x11_update_menus(0);
gui_mch_update();
if (!is_input_buf_empty()) /* Got char, return immediately */
return OK;
else if (wtime == 0) /* Don't wait for char */
return FAIL;
else if (wtime > 0)
{
timer = XtAppAddTimeOut(app_context, (long_u)wtime, gui_x11_timer_cb,
&timed_out);
}
else
{
/* We are waiting for ever, so update swap files after p_ut msec's */
updatescript_timer = XtAppAddTimeOut(app_context, (long_u)p_ut,
gui_x11_timer_cb, &do_updatescript);
}
while (!timed_out)
{
/*
* Don't use gui_mch_update() because then we will spin-lock until a
* char arrives, instead we use XtAppProcessEvent() to hang until an
* event arrives. No need to check for input_buf_full because we are
* returning as soon as it contains a single char. Note that
* XtAppNextEvent() may not be used because it will not return after a
* timer event has arrived -- webb
*/
XtAppProcessEvent(app_context, XtIMAll);
/*
* If no characters arrive within 'updatetime' milli-seconds, flush all
* the swap files to disk.
*/
if (do_updatescript)
{
updatescript(0);
do_updatescript = FALSE;
updatescript_timer = (XtIntervalId)0;
}
if (!is_input_buf_empty())
{
if (timer != (XtIntervalId)0 && !timed_out)
XtRemoveTimeOut(timer);
if (updatescript_timer != (XtIntervalId)0 && !do_updatescript)
XtRemoveTimeOut(updatescript_timer);
return OK;
}
}
return FAIL;
}
/*
* Output routines.
*/
/* Flush any output to the screen */
void
gui_mch_flush()
{
XFlush(gui.dpy);
}
/*
* Clear a rectangular region of the screen from text pos (row1, col1) to
* (row2, col2) inclusive.
*/
void
gui_mch_clear_block(row1, col1, row2, col2)
int row1;
int col1;
int row2;
int col2;
{
/* Clear the selection if we are about to write over it */
if (gui.selection.state == SELECT_DONE
&& row2 >= gui.selection.start.lnum
&& row1 <= gui.selection.end.lnum)
gui_mch_clear_selection();
XFillRectangle(gui.dpy, gui.wid, gui.back_gc, FILL_X(col1),
FILL_Y(row1), (col2 - col1 + 1) * gui.char_width,
(row2 - row1 + 1) * gui.char_height);
/* Invalidate cursor if it was in this block */
if (gui.cursor_row >= row1 && gui.cursor_row <= row2
&& gui.cursor_col >= col1 && gui.cursor_col <= col2)
INVALIDATE_CURSOR();
}
/*
* Output the given string at the current cursor position. If the string is
* too long to fit on the line, then it is truncated. wrap_cursor may be
* TRUE if the cursor position should be wrapped when the end of the line is
* reached, however the string will still be truncated and not continue on the
* next line. is_cursor should only be TRUE when this function is being called
* to actually draw the cursor.
*/
void
gui_mch_outstr_nowrap(s, len, wrap_cursor, is_cursor)
char_u *s;
int len;
int wrap_cursor;
int is_cursor;
{
GC text_gc;
long_u highlight_mask;
if (len == 0)
return;
if (len < 0)
len = STRLEN(s);
if (is_cursor && gui.in_focus)
highlight_mask = gui.highlight_mask | HL_INVERSE;
else
highlight_mask = gui.highlight_mask;
if (highlight_mask & (HL_INVERSE | HL_STANDOUT))
text_gc = gui.back_gc;
else
text_gc = gui.text_gc;
/* Set the font */
if ((highlight_mask & (HL_BOLD | HL_STANDOUT)) && gui.bold_font != NULL)
if ((highlight_mask & HL_ITAL) && gui.boldital_font != NULL)
gui_x11_set_font(text_gc, gui.boldital_font->fid);
else
gui_x11_set_font(text_gc, gui.bold_font->fid);
else if ((highlight_mask & HL_ITAL) && gui.ital_font != NULL)
gui_x11_set_font(text_gc, gui.ital_font->fid);
else
gui_x11_set_font(text_gc, gui.norm_font->fid);
/* Set the color */
if (is_cursor && gui.in_focus)
gui_x11_set_color(text_gc, gui.cursor_pixel);
else if (highlight_mask & (HL_BOLD | HL_STANDOUT))
gui_x11_set_color(text_gc, gui.bold_pixel);
else if (highlight_mask & HL_ITAL)
gui_x11_set_color(text_gc, gui.ital_pixel);
else if (highlight_mask & HL_UNDERLINE)
gui_x11_set_color(text_gc, gui.underline_pixel);
else
gui_x11_set_color(text_gc, gui.norm_pixel);
/* Clear the selection if we are about to write over it */
if (gui.selection.state == SELECT_DONE
&& gui.row >= gui.selection.start.lnum
&& gui.row <= gui.selection.end.lnum)
gui_mch_clear_selection();
/* Draw the text */
XDrawImageString(gui.dpy, gui.wid, text_gc,
TEXT_X(gui.col), TEXT_Y(gui.row), (char *)s, len);
/* No bold font, so fake it */
if ((highlight_mask & (HL_BOLD | HL_STANDOUT)) && gui.bold_font == NULL)
XDrawString(gui.dpy, gui.wid, text_gc,
TEXT_X(gui.col) + 1, TEXT_Y(gui.row), (char *)s, len);
/* Underline the text */
if ((highlight_mask & HL_UNDERLINE)
|| ((highlight_mask & HL_ITAL) && gui.ital_font == NULL))
XDrawLine(gui.dpy, gui.wid, text_gc, FILL_X(gui.col),
FILL_Y(gui.row + 1) - 1, FILL_X(gui.col + len) - 1,
FILL_Y(gui.row + 1) - 1);
if (!is_cursor)
{
/* Invalidate the old physical cursor position if we wrote over it */
if (gui.cursor_row == gui.row && gui.cursor_col >= gui.col
&& gui.cursor_col < gui.col + len)
INVALIDATE_CURSOR();
/* Update the cursor position */
gui.col += len;
if (wrap_cursor && gui.col >= Columns)
{
gui.col = 0;
gui.row++;
}
}
}
/*
* Delete the given number of lines from the given row, scrolling up any
* text further down within the scroll region.
*/
void
gui_mch_delete_lines(row, num_lines)
int row;
int num_lines;
{
if (gui.visibility == VisibilityFullyObscured)
return; /* Can't see the window */
if (num_lines <= 0)
return;
if (row + num_lines > gui.scroll_region_bot)
{
/* Scrolled out of region, just blank the lines out */
gui_mch_clear_block(row, 0, gui.scroll_region_bot, Columns - 1);
}
else
{
XCopyArea(gui.dpy, gui.wid, gui.wid, gui.text_gc,
FILL_X(0), FILL_Y(row + num_lines),
gui.char_width * Columns,
gui.char_height * (gui.scroll_region_bot - row - num_lines + 1),
FILL_X(0), FILL_Y(row));
/* Update gui.cursor_row if the cursor scrolled or copied over */
if (gui.cursor_row >= row)
{
if (gui.cursor_row < row + num_lines)
INVALIDATE_CURSOR();
else if (gui.cursor_row <= gui.scroll_region_bot)
gui.cursor_row -= num_lines;
}
gui_mch_clear_block(gui.scroll_region_bot - num_lines + 1, 0,
gui.scroll_region_bot, Columns - 1);
gui_x11_check_copy_area();
}
}
/*
* Insert the given number of lines before the given row, scrolling down any
* following text within the scroll region.
*/
void
gui_mch_insert_lines(row, num_lines)
int row;
int num_lines;
{
if (gui.visibility == VisibilityFullyObscured)
return; /* Can't see the window */
if (num_lines <= 0)
return;
if (row + num_lines > gui.scroll_region_bot)
{
/* Scrolled out of region, just blank the lines out */
gui_mch_clear_block(row, 0, gui.scroll_region_bot, Columns - 1);
}
else
{
XCopyArea(gui.dpy, gui.wid, gui.wid, gui.text_gc,
FILL_X(0), FILL_Y(row),
gui.char_width * Columns,
gui.char_height * (gui.scroll_region_bot - row - num_lines + 1),
FILL_X(0), FILL_Y(row + num_lines));
/* Update gui.cursor_row if the cursor scrolled or copied over */
if (gui.cursor_row >= gui.row)
{
if (gui.cursor_row <= gui.scroll_region_bot - num_lines)
gui.cursor_row += num_lines;
else if (gui.cursor_row <= gui.scroll_region_bot)
INVALIDATE_CURSOR();
}
gui_mch_clear_block(row, 0, row + num_lines - 1, Columns - 1);
gui_x11_check_copy_area();
}
}
/*
* Scroll the text between gui.scroll_region_top & gui.scroll_region_bot by the
* number of lines given. Positive scrolls down (text goes up) and negative
* scrolls up (text goes down).
*/
static void
gui_x11_check_copy_area()
{
XEvent event;
XGraphicsExposeEvent *gevent;
if (gui.visibility != VisibilityPartiallyObscured)
return;
XFlush(gui.dpy);
/* Wait to check whether the scroll worked or not */
for (;;)
{
if (XCheckTypedEvent(gui.dpy, NoExpose, &event))
return; /* The scroll worked. */
if (XCheckTypedEvent(gui.dpy, GraphicsExpose, &event))
{
gevent = (XGraphicsExposeEvent *)&event;
gui_redraw(gevent->x, gevent->y, gevent->width, gevent->height);
if (gevent->count == 0)
return; /* This was the last expose event */
}
XSync(gui.dpy, False);
}
}
/*
* X Selection stuff, for cutting and pasting text to other windows.
*/
static void
gui_x11_request_selection_cb(w, success, selection, type, value, length, format)
Widget w;
XtPointer success;
Atom *selection;
Atom *type;
XtPointer value;
long_u *length;
int *format;
{
int motion_type;
long_u len;
char_u *p;
if (value == NULL || *length == 0)
{
gui_free_selection(); /* ??? */
*(int *)success = FALSE;
return;
}
motion_type = MCHAR;
p = (char_u *)value;
len = *length;
if (*type == gui.selection.atom)
{
motion_type = *p++;
len--;
}
gui_yank_selection(motion_type, p, len);
XtFree((char *)value);
*(int *)success = TRUE;
}
void
gui_request_selection()
{
XEvent event;
Atom type = gui.selection.atom;
int success;
int i;
for (i = 0; i < 2; i++)
{
XtGetSelectionValue(vimShell, XA_PRIMARY, type,
gui_x11_request_selection_cb, (XtPointer)&success, CurrentTime);
/* Do we need this?: */
XFlush(gui.dpy);
/*
* Wait for result of selection request, otherwise if we type more
* characters, then they will appear before the one that requested the
* paste! Don't worry, we will catch up with any other events later.
*/
for (;;)
{
if (XCheckTypedEvent(gui.dpy, SelectionNotify, &event))
break;
/* Do we need this?: */
XSync(gui.dpy, False);
}
XtDispatchEvent(&event);
if (success)
return;
type = XA_STRING;
}
}
static Boolean
gui_x11_convert_selection_cb(w, selection, target, type, value, length, format)
Widget w;
Atom *selection;
Atom *target;
Atom *type;
XtPointer *value;
long_u *length;
int *format;
{
char_u *string;
char_u *result;
int motion_type;
if (!gui.selection.owned)
return False; /* Shouldn't ever happen */
if (*target == gui.selection.atom)
{
gui_get_selection();
motion_type = gui_convert_selection(&string, length);
if (motion_type < 0)
return False;
(*length)++;
*value = XtMalloc(*length);
result = (char_u *)*value;
if (result == NULL)
return False;
result[0] = motion_type;
vim_memmove(result + 1, string, (size_t)(*length - 1));
*type = *target;
*format = 8; /* 8 bits per char */
return True;
}
else if (*target == XA_STRING)
{
gui_get_selection();
motion_type = gui_convert_selection(&string, length);
if (motion_type < 0)
return False;
*value = XtMalloc(*length);
result = (char_u *)*value;
if (result == NULL)
return False;
vim_memmove(result, string, (size_t)(*length));
*type = *target;
*format = 8; /* 8 bits per char */
return True;
}
else
return False;
}
static void
gui_x11_lose_ownership_cb(w, selection)
Widget w;
Atom *selection;
{
gui_lose_selection();
}
void
gui_mch_lose_selection()
{
XtDisownSelection(vimShell, XA_PRIMARY, CurrentTime);
gui_mch_clear_selection();
}
int
gui_mch_own_selection()
{
if (XtOwnSelection(vimShell, XA_PRIMARY, CurrentTime,
gui_x11_convert_selection_cb, gui_x11_lose_ownership_cb,
NULL) == False)
return FAIL;
return OK;
}
/*
* Menu stuff.
*/
void
gui_x11_menu_cb(w, client_data, call_data)
Widget w;
XtPointer client_data, call_data;
{
gui_menu_cb((GuiMenu *)client_data);
}
/*
* Used recursively by gui_x11_update_menus (see below)
*/
static void
gui_x11_update_menus_recurse(menu, mode)
GuiMenu *menu;
int mode;
{
while (menu)
{
if (menu->modes & mode)
{
if (vim_strchr(p_guioptions, GO_GREY) != NULL)
XtSetSensitive(menu->id, True);
else
XtManageChild(menu->id);
gui_x11_update_menus_recurse(menu->children, mode);
}
else
{
if (vim_strchr(p_guioptions, GO_GREY) != NULL)
XtSetSensitive(menu->id, False);
else
XtUnmanageChild(menu->id);
}
menu = menu->next;
}
}
/*
* Make sure only the valid menu items appear for this mode. If
* force_menu_update is not TRUE, then we only do this if the mode has changed
* since last time. If "modes" is not 0, then we use these modes instead.
*/
void
gui_x11_update_menus(modes)
int modes;
{
static int prev_mode = -1;
int mode = 0;
if (modes != 0x0)
mode = modes;
else if (VIsual_active)
mode = MENU_VISUAL_MODE;
else if (State & NORMAL)
mode = MENU_NORMAL_MODE;
else if (State & INSERT)
mode = MENU_INSERT_MODE;
else if (State & CMDLINE)
mode = MENU_CMDLINE_MODE;
if (force_menu_update || mode != prev_mode)
{
gui_x11_update_menus_recurse(gui.root_menu, mode);
prev_mode = mode;
force_menu_update = FALSE;
}
}
/*
* Function called when window closed. Preserve files and exit.
* Should put up a requester!
*/
/*ARGSUSED*/
static void
gui_x11_wm_protocol_handler(w, client_data, event, bool)
Widget w;
XtPointer client_data;
XEvent *event;
Boolean *bool;
{
/*
* On some HPUX system with Motif 1.2 this function is somehow called when
* starting up. This if () avoids an unexpected exit.
*/
if (event->type != ClientMessage ||
((XClientMessageEvent *)event)->data.l[0] != Atom_WM_DELETE_WINDOW)
return;
STRCPY(IObuff, "Vim: Window closed\n");
preserve_exit(); /* preserve files and exit */
}
/*
* Compare two screen positions ala strcmp()
*/
static int
gui_x11_compare_pos(row1, col1, row2, col2)
short_u row1;
short_u col1;
short_u row2;
short_u col2;
{
if (row1 > row2) return( 1);
if (row1 < row2) return(-1);
if (col1 > col2) return( 1);
if (col1 < col2) return(-1);
return( 0);
}
/*
* Start out the selection
*/
void
gui_x11_start_selection(button, x, y, repeated_click, modifiers)
int button;
int x;
int y;
int repeated_click;
int_u modifiers;
{
GuiSelection *gs = &gui.selection;
if (gs->state == SELECT_DONE)
gui_mch_clear_selection();
gs->start.lnum = Y_2_ROW(y);
gs->start.col = X_2_COL(x);
gs->end = gs->start;
gs->origin_row = gs->start.lnum;
gs->state = SELECT_IN_PROGRESS;
if (repeated_click)
{
if (++(gs->mode) > SELECT_MODE_LINE)
gs->mode = SELECT_MODE_CHAR;
}
else
gs->mode = SELECT_MODE_CHAR;
/* clear the cursor until the selection is made */
gui_undraw_cursor();
switch (gs->mode)
{
case SELECT_MODE_CHAR:
gs->origin_start_col = gs->start.col;
gs->word_end_col = gui_x11_get_line_end(gs->start.lnum);
break;
case SELECT_MODE_WORD:
gui_x11_get_word_boundaries(gs, gs->start.lnum, gs->start.col);
gs->origin_start_col = gs->word_start_col;
gs->origin_end_col = gs->word_end_col;
gui_x11_invert_area(gs->start.lnum, gs->word_start_col,
gs->end.lnum, gs->word_end_col);
gs->start.col = gs->word_start_col;
gs->end.col = gs->word_end_col;
break;
case SELECT_MODE_LINE:
gui_x11_invert_area(gs->start.lnum, 0, gs->start.lnum,
gui.num_cols);
gs->start.col = 0;
gs->end.col = gui.num_cols;
break;
}
gs->prev = gs->start;
#ifdef DEBUG_SELECTION
printf("Selection started at (%u,%u)\n", gs->start.lnum, gs->start.col);
#endif
}
/*
* Continue processing the selection
*/
void
gui_x11_process_selection(button, x, y, repeated_click, modifiers)
int button;
int x;
int y;
int repeated_click;
int_u modifiers;
{
GuiSelection *gs = &gui.selection;
int row, col;
int diff;
if (button == MOUSE_RELEASE)
{
/* Check to make sure we have something selected */
if (gs->start.lnum == gs->end.lnum && gs->start.col == gs->end.col)
{
gui_update_cursor();
gs->state = SELECT_CLEARED;
return;
}
#ifdef DEBUG_SELECTION
printf("Selection ended: (%u,%u) to (%u,%u)\n", gs->start.lnum,
gs->start.col, gs->end.lnum, gs->end.col);
#endif
gui_free_selection();
gui_own_selection();
gui_x11_yank_selection(gs->start.lnum, gs->start.col, gs->end.lnum,
gs->end.col);
gui_update_cursor();
gs->state = SELECT_DONE;
return;
}
row = Y_2_ROW(y);
col = X_2_COL(x);
row = check_row(row);
col = check_col(col);
if (col == gs->prev.col && row == gs->prev.lnum)
return;
/*
* When extending the selection with the right mouse button, swap the
* start and end if the position is before half the selection
*/
if (gs->state == SELECT_DONE && button == MOUSE_RIGHT)
{
/*
* If the click is before the start, or the click is inside the
* selection and the start is the closest side, set the origin to the
* end of the selection.
*/
if (gui_x11_compare_pos(row, col, gs->start.lnum, gs->start.col) < 0 ||
(gui_x11_compare_pos(row, col, gs->end.lnum, gs->end.col) < 0 &&
(((gs->start.lnum == gs->end.lnum &&
gs->end.col - col > col - gs->start.col)) ||
((diff = (gs->end.lnum - row) - (row - gs->start.lnum)) > 0 ||
(diff == 0 && col < (gs->start.col + gs->end.col) / 2)))))
{
gs->origin_row = gs->end.lnum;
gs->origin_start_col = gs->end.col - 1;
gs->origin_end_col = gs->end.col;
}
else
{
gs->origin_row = gs->start.lnum;
gs->origin_start_col = gs->start.col;
gs->origin_end_col = gs->start.col;
}
if (gs->mode == SELECT_MODE_WORD)
{
gui_x11_get_word_boundaries(gs, gs->origin_row,
gs->origin_start_col);
gs->origin_start_col = gs->word_start_col;
gs->origin_end_col = gs->word_end_col;
}
}
/* set state, for when using the right mouse button */
gs->state = SELECT_IN_PROGRESS;
#ifdef DEBUG_SELECTION
printf("Selection extending to (%d,%d)\n", row, col);
#endif
switch (gs->mode)
{
case SELECT_MODE_CHAR:
/* If we're on a different line, find where the line ends */
if (row != gs->prev.lnum)
gs->word_end_col = gui_x11_get_line_end(row);
/* See if we are before or after the origin of the selection */
if (gui_x11_compare_pos(row, col, gs->origin_row,
gs->origin_start_col) >= 0)
{
if (col >= (int)gs->word_end_col)
gui_x11_update_selection(gs, gs->origin_row,
gs->origin_start_col, row, gui.num_cols);
else
gui_x11_update_selection(gs, gs->origin_row,
gs->origin_start_col, row, col + 1);
}
else
{
if (col >= (int)gs->word_end_col)
gui_x11_update_selection(gs, row, gs->word_end_col,
gs->origin_row, gs->origin_start_col + 1);
else
gui_x11_update_selection(gs, row, col, gs->origin_row,
gs->origin_start_col + 1);
}
break;
case SELECT_MODE_WORD:
/* If we are still within the same word, do nothing */
if (row == gs->prev.lnum && col >= (int)gs->word_start_col
&& col < (int)gs->word_end_col)
return;
/* Get new word boundaries */
gui_x11_get_word_boundaries(gs, row, col);
/* Handle being after the origin point of selection */
if (gui_x11_compare_pos(row, col, gs->origin_row,
gs->origin_start_col) >= 0)
gui_x11_update_selection(gs, gs->origin_row,
gs->origin_start_col, row, gs->word_end_col);
else
gui_x11_update_selection(gs, row, gs->word_start_col,
gs->origin_row, gs->origin_end_col);
break;
case SELECT_MODE_LINE:
if (row == gs->prev.lnum)
return;
if (gui_x11_compare_pos(row, col, gs->origin_row,
gs->origin_start_col) >= 0)
gui_x11_update_selection(gs, gs->origin_row, 0, row,
gui.num_cols);
else
gui_x11_update_selection(gs, row, 0, gs->origin_row,
gui.num_cols);
break;
}
gs->prev.lnum = row;
gs->prev.col = col;
#ifdef DEBUG_SELECTION
printf("Selection is: (%u,%u) to (%u,%u)\n", gs->start.lnum,
gs->start.col, gs->end.lnum, gs->end.col);
#endif
}
/*
* Called after an Expose event to redraw the selection
*/
void
gui_x11_redraw_selection(x, y, w, h)
int x;
int y;
int w;
int h;
{
GuiSelection *gs = &gui.selection;
int row1, col1, row2, col2;
int row;
int start;
int end;
if (gs->state == SELECT_CLEARED)
return;
row1 = Y_2_ROW(y);
col1 = X_2_COL(x);
row2 = Y_2_ROW(y + h - 1);
col2 = X_2_COL(x + w - 1);
/* Limit the rows that need to be re-drawn */
if (gs->start.lnum > row1)
row1 = gs->start.lnum;
if (gs->end.lnum < row2)
row2 = gs->end.lnum;
/* Look at each row that might need to be re-drawn */
for (row = row1; row <= row2; row++)
{
/* For the first selection row, use the starting selection column */
if (row == gs->start.lnum)
start = gs->start.col;
else
start = 0;
/* For the last selection row, use the ending selection column */
if (row == gs->end.lnum)
end = gs->end.col;
else
end = gui.num_cols;
if (col1 > start)
start = col1;
if (col2 < end)
end = col2 + 1;
if (end > start)
invert_rectangle(row, start, 1, end - start);
}
}
/*
* Called from outside to clear selected region from the display
*/
void
gui_mch_clear_selection()
{
GuiSelection *gs = &gui.selection;
if (gs->state == SELECT_CLEARED)
return;
gui_x11_invert_area(gs->start.lnum, gs->start.col, gs->end.lnum,
gs->end.col);
gs->state = SELECT_CLEARED;
}
/*
* Invert a region of the display between a starting and ending row and column
*/
static void
gui_x11_invert_area(row1, col1, row2, col2)
int row1;
int col1;
int row2;
int col2;
{
/* Swap the from and to positions so the from is always before */
if (gui_x11_compare_pos(row1, col1, row2, col2) > 0)
{
int tmp_row, tmp_col;
tmp_row = row1;
tmp_col = col1;
row1 = row2;
col1 = col2;
row2 = tmp_row;
col2 = tmp_col;
}
/* If all on the same line, do it the easy way */
if (row1 == row2)
{
invert_rectangle(row1, col1, 1, col2 - col1);
return;
}
/* Handle a piece of the first line */
if (col1 > 0)
{
invert_rectangle(row1, col1, 1, gui.num_cols - col1);
row1++;
}
/* Handle a piece of the last line */
if (col2 < gui.num_cols - 1)
{
invert_rectangle(row2, 0, 1, col2);
row2--;
}
/* Handle the rectangle thats left */
if (row2 >= row1)
invert_rectangle(row1, 0, row2 - row1 + 1, gui.num_cols);
}
/*
* Yank the currently selected area into the special selection buffer so it
* will be available for pasting.
*/
static void
gui_x11_yank_selection(row1, col1, row2, col2)
int row1;
int col1;
int row2;
int col2;
{
char_u *buffer;
char_u *bufp;
int row;
int start_col;
int end_col;
int line_end_col;
int add_newline_flag = FALSE;
/* Create a temporary buffer for storing the text */
buffer = lalloc((row2 - row1 + 1) * gui.num_cols + 1, TRUE);
if (buffer == NULL)
return; /* Should there be an error message here? */
/* Process each row in the selection */
for (bufp = buffer, row = row1; row <= row2; row++)
{
if (row == row1)
start_col = col1;
else
start_col = 0;
if (row == row2)
end_col = col2;
else
end_col = gui.num_cols;
line_end_col = gui_x11_get_line_end(row);
/* See if we need to nuke some trailing whitespace */
if (end_col >= gui.num_cols && (row < row2 || end_col > line_end_col))
{
/* Get rid of trailing whitespace */
end_col = line_end_col;
if (end_col < start_col)
end_col = start_col;
/* If the last line extended to the end, add an extra newline */
if (row == row2)
add_newline_flag = TRUE;
}
/* If after the first row, we need to always add a newline */
if (row > row1)
*bufp++ = NL;
if (gui.row < screen_Rows && end_col <= screen_Columns)
{
STRNCPY(bufp, &LinePointers[row][start_col], end_col - start_col);
bufp += end_col - start_col;
}
}
/* Add a newline at the end if the selection ended there */
if (add_newline_flag)
*bufp++ = NL;
gui_yank_selection(MCHAR, buffer, bufp - buffer);
vim_free(buffer);
}
/*
* Find the starting and ending positions of the word at the given row and
* column.
*/
static void
gui_x11_get_word_boundaries(gs, row, col)
GuiSelection *gs;
int row;
int col;
{
char start_class;
int temp_col;
if (row >= screen_Rows || col >= screen_Columns)
return;
start_class = char_class(LinePointers[row][col]);
temp_col = col;
for ( ; temp_col > 0; temp_col--)
if (char_class(LinePointers[row][temp_col - 1]) != start_class)
break;
gs->word_start_col = temp_col;
temp_col = col;
for ( ; temp_col < screen_Columns; temp_col++)
if (char_class(LinePointers[row][temp_col]) != start_class)
break;
gs->word_end_col = temp_col;
#ifdef DEBUG_SELECTION
printf("Current word: col %u to %u\n", gs->word_start_col,
gs->word_end_col);
#endif
}
/*
* Find the column position for the last non-whitespace character on the given
* line.
*/
static int
gui_x11_get_line_end(row)
int row;
{
int i;
if (row >= screen_Rows)
return 0;
for (i = screen_Columns; i > 0; i--)
if (LinePointers[row][i - 1] != ' ')
break;
return i;
}
/*
* Update the currently selected region by adding and/or subtracting from the
* beginning or end and inverting the changed area(s).
*/
static void
gui_x11_update_selection(gs, row1, col1, row2, col2)
GuiSelection *gs;
int row1;
int col1;
int row2;
int col2;
{
/* See if we changed at the beginning of the selection */
if (row1 != gs->start.lnum || col1 != gs->start.col)
{
gui_x11_invert_area(row1, col1, gs->start.lnum, gs->start.col);
gs->start.lnum = row1;
gs->start.col = col1;
}
/* See if we changed at the end of the selection */
if (row2 != gs->end.lnum || col2 != gs->end.col)
{
gui_x11_invert_area(row2, col2, gs->end.lnum, gs->end.col);
gs->end.lnum = row2;
gs->end.col = col2;
}
}